/****************************************************************************
 * fs/procfs/fs_procfsproc.c
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <sys/types.h>
#include <sys/stat.h>

#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>

#ifdef CONFIG_SCHED_CRITMONITOR
#  include <time.h>
#endif

#include <nuttx/irq.h>
#include <nuttx/arch.h>
#include <nuttx/sched.h>
#include <nuttx/kmalloc.h>
#include <nuttx/environ.h>
#include <nuttx/fs/fs.h>
#include <nuttx/fs/procfs.h>
#include <nuttx/fs/dirent.h>

#if defined(CONFIG_SCHED_CPULOAD) || defined(CONFIG_SCHED_CRITMONITOR)
#  include <nuttx/clock.h>
#endif

#if !defined(CONFIG_DISABLE_MOUNTPOINT) && defined(CONFIG_FS_PROCFS)
#ifndef CONFIG_FS_PROCFS_EXCLUDE_PROCESS

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/* See include/nuttx/sched.h: */

#undef HAVE_GROUPID

#if defined(CONFIG_SCHED_HAVE_PARENT) && defined(CONFIG_SCHED_CHILD_STATUS)
#  define HAVE_GROUPID  1
#endif

#ifdef CONFIG_DISABLE_PTHREAD
#  undef HAVE_GROUPID
#endif

/* Determines the size of an intermediate buffer that must be large enough
 * to handle the longest line generated by this logic.
 */

#define STATUS_LINELEN 32

/****************************************************************************
 * Private Type Definitions
 ****************************************************************************/

/* This enumeration identifies all of the task/thread nodes that can be
 * accessed via the procfs file system.
 */

enum proc_node_e
{
  PROC_LEVEL0 = 0,                    /* The top-level directory */
  PROC_STATUS,                        /* Task/thread status */
  PROC_CMDLINE,                       /* Task command line */
#ifdef CONFIG_SCHED_CPULOAD
  PROC_LOADAVG,                       /* Average CPU utilization */
#endif
#ifdef CONFIG_SCHED_CRITMONITOR
  PROC_CRITMON,                       /* Critical section monitor */
#endif
  PROC_STACK,                         /* Task stack info */
  PROC_GROUP,                         /* Group directory */
  PROC_GROUP_STATUS,                  /* Task group status */
  PROC_GROUP_FD                       /* Group file descriptors */
#if !defined(CONFIG_DISABLE_ENVIRON) && !defined(CONFIG_FS_PROCFS_EXCLUDE_ENVIRON)
  , PROC_GROUP_ENV                    /* Group environment variables */
#endif
};

/* This structure associates a relative path name with an node in the task
 * procfs
 */

struct proc_node_s
{
  FAR const char *relpath;            /* Relative path to the node */
  FAR const char *name;               /* Terminal node segment name */
  uint8_t node;                       /* Type of node (see enum proc_node_e) */
  uint8_t dtype;                      /* dirent type (see include/dirent.h) */
};

/* This structure describes one open "file" */

struct proc_file_s
{
  struct procfs_file_s base;          /* Base open file structure */
  FAR const struct proc_node_s *node; /* Describes the file node */
  pid_t pid;                          /* Task/thread ID */
  char line[STATUS_LINELEN];          /* Pre-allocated buffer for formatted lines */
};

/* This structure describes one open "directory" */

struct proc_dir_s
{
  struct procfs_dir_priv_s base;      /* Base directory private data */
  FAR const struct proc_node_s *node; /* Directory node description */
  pid_t pid;                          /* ID of task/thread for attributes */
};

/* This structure used with the env_foreach() callback */

struct proc_envinfo_s
{
  FAR struct proc_file_s *procfile;
  FAR char *buffer;
  FAR off_t offset;
  size_t buflen;
  size_t remaining;
  size_t totalsize;
};

/****************************************************************************
 * Private Data
 ****************************************************************************/

static FAR const char *g_policy[4] =
{
  "SCHED_FIFO", "SCHED_RR", "SCHED_SPORADIC", "SCHED_OTHER"
};

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

/* Helpers */

static FAR const struct proc_node_s *
               proc_findnode(FAR const char *relpath);
static ssize_t proc_status(FAR struct proc_file_s *procfile,
                 FAR struct tcb_s *tcb, FAR char *buffer, size_t buflen,
                 off_t offset);
static ssize_t proc_cmdline(FAR struct proc_file_s *procfile,
                 FAR struct tcb_s *tcb, FAR char *buffer, size_t buflen,
                 off_t offset);
#ifdef CONFIG_SCHED_CPULOAD
static ssize_t proc_loadavg(FAR struct proc_file_s *procfile,
                 FAR struct tcb_s *tcb, FAR char *buffer, size_t buflen,
                 off_t offset);
#endif
#ifdef CONFIG_SCHED_CRITMONITOR
static ssize_t proc_critmon(FAR struct proc_file_s *procfile,
                 FAR struct tcb_s *tcb, FAR char *buffer, size_t buflen,
                 off_t offset);
#endif
static ssize_t proc_stack(FAR struct proc_file_s *procfile,
                 FAR struct tcb_s *tcb, FAR char *buffer, size_t buflen,
                 off_t offset);
static ssize_t proc_groupstatus(FAR struct proc_file_s *procfile,
                 FAR struct tcb_s *tcb, FAR char *buffer, size_t buflen,
                 off_t offset);
static ssize_t proc_groupfd(FAR struct proc_file_s *procfile,
                 FAR struct tcb_s *tcb, FAR char *buffer, size_t buflen,
                 off_t offset);
#if !defined(CONFIG_DISABLE_ENVIRON) && !defined(CONFIG_FS_PROCFS_EXCLUDE_ENVIRON)
static int     proc_groupenv_callback(FAR void *arg, FAR const char *pair);
static ssize_t proc_groupenv(FAR struct proc_file_s *procfile,
                 FAR struct tcb_s *tcb, FAR char *buffer, size_t buflen,
                 off_t offset);
#endif

/* File system methods */

static int     proc_open(FAR struct file *filep, FAR const char *relpath,
                 int oflags, mode_t mode);
static int     proc_close(FAR struct file *filep);
static ssize_t proc_read(FAR struct file *filep, FAR char *buffer,
                 size_t buflen);

static int     proc_dup(FAR const struct file *oldp,
                 FAR struct file *newp);

static int     proc_opendir(const char *relpath,
                 FAR struct fs_dirent_s *dir);
static int     proc_closedir(FAR struct fs_dirent_s *dir);
static int     proc_readdir(FAR struct fs_dirent_s *dir);
static int     proc_rewinddir(FAR struct fs_dirent_s *dir);

static int     proc_stat(FAR const char *relpath, FAR struct stat *buf);

/****************************************************************************
 * Private Data
 ****************************************************************************/

/****************************************************************************
 * Public Data
 ****************************************************************************/

/* See fs_mount.c -- this structure is explicitly externed there.
 * We use the old-fashioned kind of initializers so that this will compile
 * with any compiler.
 */

const struct procfs_operations proc_operations =
{
  proc_open,          /* open */
  proc_close,         /* close */
  proc_read,          /* read */
  NULL,               /* write */

  proc_dup,           /* dup */

  proc_opendir,       /* opendir */
  proc_closedir,      /* closedir */
  proc_readdir,       /* readdir */
  proc_rewinddir,     /* rewinddir */

  proc_stat           /* stat */
};

/* These structures provide information about every node */

static const struct proc_node_s g_level0node =
{
  "",             "",        (uint8_t)PROC_LEVEL0,       DTYPE_DIRECTORY   /* Top-level directory */
};

static const struct proc_node_s g_status =
{
  "status",       "status",  (uint8_t)PROC_STATUS,       DTYPE_FILE        /* Task/thread status */
};

static const struct proc_node_s g_cmdline =
{
  "cmdline",      "cmdline", (uint8_t)PROC_CMDLINE,      DTYPE_FILE        /* Task command line */
};

#ifdef CONFIG_SCHED_CPULOAD
static const struct proc_node_s g_loadavg =
{
  "loadavg",       "loadavg", (uint8_t)PROC_LOADAVG,     DTYPE_FILE        /* Average CPU utilization */
};
#endif

#ifdef CONFIG_SCHED_CRITMONITOR
static const struct proc_node_s g_critmon =
{
  "critmon",       "critmon", (uint8_t)PROC_CRITMON,     DTYPE_FILE        /* Critical Section Monitor */
};
#endif

static const struct proc_node_s g_stack =
{
  "stack",        "stack",   (uint8_t)PROC_STACK,        DTYPE_FILE        /* Task stack info */
};

static const struct proc_node_s g_group =
{
  "group",        "group",   (uint8_t)PROC_GROUP,        DTYPE_DIRECTORY   /* Group directory */
};

static const struct proc_node_s g_groupstatus =
{
  "group/status", "status",  (uint8_t)PROC_GROUP_STATUS, DTYPE_FILE        /* Task group status */
};

static const struct proc_node_s g_groupfd =
{
  "group/fd",     "fd",      (uint8_t)PROC_GROUP_FD,     DTYPE_FILE        /* Group file descriptors */
};

#if !defined(CONFIG_DISABLE_ENVIRON) && !defined(CONFIG_FS_PROCFS_EXCLUDE_ENVIRON)
static const struct proc_node_s g_groupenv =
{
  "group/env",    "env",     (uint8_t)PROC_GROUP_ENV,    DTYPE_FILE        /* Group environment variables */
};

#endif

/* This is the list of all nodes */

static FAR const struct proc_node_s * const g_nodeinfo[] =
{
  &g_status,       /* Task/thread status */
  &g_cmdline,      /* Task command line */
#ifdef CONFIG_SCHED_CPULOAD
  &g_loadavg,      /* Average CPU utilization */
#endif
#ifdef CONFIG_SCHED_CRITMONITOR
  &g_critmon,      /* Critical section Monitor */
#endif
  &g_stack,        /* Task stack info */
  &g_group,        /* Group directory */
  &g_groupstatus,  /* Task group status */
  &g_groupfd       /* Group file descriptors */
#if !defined(CONFIG_DISABLE_ENVIRON) && !defined(CONFIG_FS_PROCFS_EXCLUDE_ENVIRON)
  , &g_groupenv    /* Group environment variables */
#endif
};

#define PROC_NNODES (sizeof(g_nodeinfo)/sizeof(FAR const struct proc_node_s * const))

/* This is the list of all level0 nodes */

static const struct proc_node_s * const g_level0info[] =
{
  &g_status,       /* Task/thread status */
  &g_cmdline,      /* Task command line */
#ifdef CONFIG_SCHED_CPULOAD
  &g_loadavg,      /* Average CPU utilization */
#endif
#ifdef CONFIG_SCHED_CRITMONITOR
  &g_critmon,      /* Critical section monitor */
#endif
  &g_stack,        /* Task stack info */
  &g_group,        /* Group directory */
};
#define PROC_NLEVEL0NODES (sizeof(g_level0info)/sizeof(FAR const struct proc_node_s * const))

/* This is the list of all group sub-directory nodes */

static FAR const struct proc_node_s * const g_groupinfo[] =
{
  &g_groupstatus,  /* Task group status */
  &g_groupfd       /* Group file descriptors */
#if !defined(CONFIG_DISABLE_ENVIRON) && !defined(CONFIG_FS_PROCFS_EXCLUDE_ENVIRON)
  , &g_groupenv    /* Group environment variables */
#endif
};
#define PROC_NGROUPNODES (sizeof(g_groupinfo)/sizeof(FAR const struct proc_node_s * const))

/* Names of task/thread states */

static FAR const char *g_statenames[] =
{
  "Invalid",
  "Waiting,Unlock",
  "Ready",
#ifdef CONFIG_SMP
  "Assigned",
#endif
  "Running",
  "Inactive",
  "Waiting,Semaphore",
  "Waiting,Signal"
#ifndef CONFIG_DISABLE_MQUEUE
  , "Waiting,MQ empty"
  , "Waiting,MQ full"
#endif
#ifdef CONFIG_SIG_SIGSTOP_ACTION
  , "Stopped"
#endif
};

static FAR const char *g_ttypenames[4] =
{
  "Task",
  "pthread",
  "Kthread",
  "Invalid"
};

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: proc_findnode
 ****************************************************************************/

static FAR const struct proc_node_s *proc_findnode(FAR const char *relpath)
{
  int i;

  /* Search every string in g_nodeinfo or until a match is found */

  for (i = 0; i < PROC_NNODES; i++)
    {
      size_t len = strlen(g_nodeinfo[i]->relpath);

      if (strncmp(g_nodeinfo[i]->relpath, relpath, len) == 0 &&
          (relpath[len] == '\0' || (relpath[len] == '/' &&
           relpath[len + 1] == '\0' &&
           g_nodeinfo[i]->dtype == DTYPE_DIRECTORY)))
        {
          return g_nodeinfo[i];
        }
    }

  /* Not found */

  return NULL;
}

/****************************************************************************
 * Name: proc_status
 *
 * Description:
 *   Format:
 *
 *            111111111122222222223
 *   123456789012345678901234567890
 *   Name:       xxxx...            Task/thread name (See
 *                                  CONFIG_TASK_NAME_SIZE)
 *   Type:       xxxxxxx            {Task, pthread, Kthread, Invalid}
 *   PPID:       xxxxx              Parent thread ID
 *   Group:      xxxxx              Group ID
 *   CPU:        xxx                CPU (CONFIG_SMP only)
 *   State:      xxxxxxxx,xxxxxxxxx {Invalid, Waiting, Ready, Running,
 *                                   Inactive},
 *                                  {Unlock, Semaphore, Signal, MQ empty,
 *                                   MQ full}
 *   Flags:      xxx                N,P,X
 *   Priority:   nnn                Decimal, 0-255
 *   Scheduler:  xxxxxxxxxxxxxx     {SCHED_FIFO, SCHED_RR, SCHED_SPORADIC,
 *                                   SCHED_OTHER}
 *   Sigmask:    nnnnnnnn           Hexadecimal, 32-bit
 *
 ****************************************************************************/

static ssize_t proc_status(FAR struct proc_file_s *procfile,
                           FAR struct tcb_s *tcb, FAR char *buffer,
                           size_t buflen, off_t offset)
{
#ifdef CONFIG_SCHED_HAVE_PARENT
  FAR struct task_group_s *group;
#endif
  FAR const char *policy;
  FAR const char *name;
  size_t remaining;
  size_t linesize;
  size_t copysize;
  size_t totalsize;

  remaining = buflen;
  totalsize = 0;

  /* Show the task name */

#if CONFIG_TASK_NAME_SIZE > 0
  name       = tcb->name;
#else
  name       = "<noname>";
#endif
  linesize   = snprintf(procfile->line, STATUS_LINELEN, "%-12s%s\n",
                        "Name:", name);
  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining,
                             &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }

  /* Show the thread type */

  linesize   = snprintf(procfile->line, STATUS_LINELEN, "%-12s%s\n", "Type:",
                        g_ttypenames[(tcb->flags & TCB_FLAG_TTYPE_MASK) >>
                        TCB_FLAG_TTYPE_SHIFT]);
  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining,
                             &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }

#ifdef CONFIG_SCHED_HAVE_PARENT
  group = tcb->group;
  DEBUGASSERT(group != NULL);

#ifdef HAVE_GROUPID
  linesize   = snprintf(procfile->line, STATUS_LINELEN, "%-12s%d\n",
                        "Group:", group->tg_pgrpid);
#else
  linesize   = snprintf(procfile->line, STATUS_LINELEN, "%-12s%d\n", "PPID:",
                        group->tg_ppid);
#endif

  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining,
                             &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }
#endif

#ifdef CONFIG_SMP
  if (tcb->task_state >= FIRST_ASSIGNED_STATE &&
      tcb->task_state <= LAST_ASSIGNED_STATE)
    {
      linesize   = snprintf(procfile->line, STATUS_LINELEN, "%-12s%d\n",
                            "CPU:", tcb->cpu);
    }
  else
    {
      linesize   = snprintf(procfile->line, STATUS_LINELEN, "%-12s---\n",
                            "CPU:");
    }

  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining,
                             &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }
#endif

  /* Show the thread state */

  linesize   = snprintf(procfile->line, STATUS_LINELEN, "%-12s%s\n",
                        "State:", g_statenames[tcb->task_state]);
  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining,
                             &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }

  /* Show task flags */

  linesize   = snprintf(procfile->line, STATUS_LINELEN,
                        "%-12s%c%c%c\n", "Flags:",
                        tcb->flags & TCB_FLAG_NONCANCELABLE ? 'N' : '-',
                        tcb->flags & TCB_FLAG_CANCEL_PENDING ? 'P' : '-',
                        tcb->flags & TCB_FLAG_EXIT_PROCESSING ? 'P' : '-');

  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining,
                             &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }

  /* Show the thread priority */

#ifdef CONFIG_PRIORITY_INHERITANCE
  linesize   = snprintf(procfile->line, STATUS_LINELEN,
                        "%-12s%d (%d)\n", "Priority:",
                        tcb->sched_priority, tcb->base_priority);
#else
  linesize   = snprintf(procfile->line, STATUS_LINELEN, "%-12s%d\n",
                        "Priority:", tcb->sched_priority);
#endif
  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining,
                             &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }

  /* Show the scheduler policy */

  policy     = g_policy[(tcb->flags & TCB_FLAG_POLICY_MASK) >>
                        TCB_FLAG_POLICY_SHIFT];
  linesize   = snprintf(procfile->line, STATUS_LINELEN, "%-12s%s\n",
                        "Scheduler:", policy);
  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining,
                             &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }

  /* Show the signal mask */

  linesize = snprintf(procfile->line, STATUS_LINELEN, "%-12s%08x\n",
                      "SigMask:", tcb->sigprocmask);
  copysize = procfs_memcpy(procfile->line, linesize, buffer, remaining,
                           &offset);

  totalsize += copysize;
  return totalsize;
}

/****************************************************************************
 * Name: proc_cmdline
 ****************************************************************************/

static ssize_t proc_cmdline(FAR struct proc_file_s *procfile,
                            FAR struct tcb_s *tcb, FAR char *buffer,
                            size_t buflen, off_t offset)
{
  FAR struct task_tcb_s *ttcb;
  FAR const char *name;
  FAR char **argv;
  size_t remaining;
  size_t linesize;
  size_t copysize;
  size_t totalsize;

  remaining = buflen;
  totalsize = 0;

  /* Show the task name */

#if CONFIG_TASK_NAME_SIZE > 0
  name       = tcb->name;
#else
  name       = "<noname>";
#endif
  linesize   = strlen(name);
  memcpy(procfile->line, name, linesize);
  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining,
                             &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }

#ifndef CONFIG_DISABLE_PTHREAD
  /* Show the pthread argument */

  if ((tcb->flags & TCB_FLAG_TTYPE_MASK) == TCB_FLAG_TTYPE_PTHREAD)
    {
      FAR struct pthread_tcb_s *ptcb = (FAR struct pthread_tcb_s *)tcb;

      linesize   = snprintf(procfile->line, STATUS_LINELEN, " 0x%p\n",
                            ptcb->arg);
      copysize   = procfs_memcpy(procfile->line, linesize, buffer,
                                 remaining, &offset);

      totalsize += copysize;
      buffer    += copysize;
      remaining -= copysize;

      return totalsize;
    }
#endif

  /* Show the task argument list (skipping over the name) */

  ttcb = (FAR struct task_tcb_s *)tcb;

  for (argv = ttcb->argv + 1; *argv; argv++)
    {
      linesize   = snprintf(procfile->line, STATUS_LINELEN, " %s", *argv);
      copysize   = procfs_memcpy(procfile->line, linesize, buffer,
                                 remaining, &offset);

      totalsize += copysize;
      buffer    += copysize;
      remaining -= copysize;

      if (totalsize >= buflen)
        {
          return totalsize;
        }
    }

  linesize   = snprintf(procfile->line, STATUS_LINELEN, "\n");
  copysize   = procfs_memcpy(procfile->line, linesize, buffer,
                             remaining, &offset);

  totalsize += copysize;
  return totalsize;
}

/****************************************************************************
 * Name: proc_loadavg
 ****************************************************************************/

#ifdef CONFIG_SCHED_CPULOAD
static ssize_t proc_loadavg(FAR struct proc_file_s *procfile,
                            FAR struct tcb_s *tcb, FAR char *buffer,
                            size_t buflen, off_t offset)
{
  struct cpuload_s cpuload;
  uint32_t intpart;
  uint32_t fracpart;
  size_t linesize;
  size_t copysize;

  /* Sample the counts for the thread.  clock_cpuload should only fail if
   * the PID is not valid.  This could happen if the thread exited sometime
   * after the procfs entry was opened.
   */

  clock_cpuload(procfile->pid, &cpuload);

  /* On the simulator, you may hit cpuload.total == 0, but probably never on
   * real hardware.
   */

  if (cpuload.total > 0)
    {
      uint32_t tmp;

      tmp      = (1000 * cpuload.active) / cpuload.total;
      intpart  = tmp / 10;
      fracpart = tmp - 10 * intpart;
    }
  else
    {
      intpart  = 0;
      fracpart = 0;
    }

  linesize = snprintf(procfile->line, STATUS_LINELEN, "%3d.%01d%%",
                      intpart, fracpart);
  copysize = procfs_memcpy(procfile->line, linesize, buffer, buflen,
                           &offset);

  return copysize;
}
#endif

/****************************************************************************
 * Name: proc_critmon
 ****************************************************************************/

#ifdef CONFIG_SCHED_CRITMONITOR
static ssize_t proc_critmon(FAR struct proc_file_s *procfile,
                            FAR struct tcb_s *tcb, FAR char *buffer,
                            size_t buflen, off_t offset)
{
  struct timespec maxtime;
  size_t remaining;
  size_t linesize;
  size_t copysize;
  size_t totalsize;

  remaining = buflen;
  totalsize = 0;

  /* Convert the for maximum time pre-emption disabled */

  if (tcb->premp_max > 0)
    {
      up_critmon_convert(tcb->premp_max, &maxtime);
    }
  else
    {
      maxtime.tv_sec = 0;
      maxtime.tv_nsec = 0;
    }

  /* Reset the maximum */

  tcb->premp_max = 0;

  /* Generate output for maximum time pre-emption disabled */

  linesize = snprintf(procfile->line, STATUS_LINELEN, "%lu.%09lu,",
                     (unsigned long)maxtime.tv_sec,
                     (unsigned long)maxtime.tv_nsec);
  copysize = procfs_memcpy(procfile->line, linesize, buffer, buflen,
                           &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }

  /* Convert and generate output for maximum time in a critical section */

  if (tcb->crit_max > 0)
    {
      up_critmon_convert(tcb->crit_max, &maxtime);
    }
  else
    {
      maxtime.tv_sec = 0;
      maxtime.tv_nsec = 0;
    }

  /* Reset the maximum */

  tcb->crit_max = 0;

  /* Generate output for maximum time in a critical section */

  linesize = snprintf(procfile->line, STATUS_LINELEN, "%lu.%09lu\n",
                     (unsigned long)maxtime.tv_sec,
                     (unsigned long)maxtime.tv_nsec);
  copysize = procfs_memcpy(procfile->line, linesize, buffer, buflen,
                           &offset);

  totalsize += copysize;
  return totalsize;
}
#endif

/****************************************************************************
 * Name: proc_stack
 ****************************************************************************/

static ssize_t proc_stack(FAR struct proc_file_s *procfile,
                          FAR struct tcb_s *tcb, FAR char *buffer,
                          size_t buflen, off_t offset)
{
  size_t remaining;
  size_t linesize;
  size_t copysize;
  size_t totalsize;

  remaining = buflen;
  totalsize = 0;

  /* Show the stack base address */

  linesize   = snprintf(procfile->line, STATUS_LINELEN, "%-12s0x%p\n",
                        "StackBase:", tcb->adj_stack_ptr);
  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining,
                             &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }

  /* Show the stack size */

  linesize   = snprintf(procfile->line, STATUS_LINELEN, "%-12s%ld\n",
                        "StackSize:", (long)tcb->adj_stack_size);
  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining,
                             &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

#ifdef CONFIG_STACK_COLORATION
  if (totalsize >= buflen)
    {
      return totalsize;
    }

  /* Show the stack size */

  linesize   = snprintf(procfile->line, STATUS_LINELEN, "%-12s%ld\n",
                        "StackUsed:", (long)up_check_tcbstack(tcb));
  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining,
                             &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;
#endif

  return totalsize;
}

/****************************************************************************
 * Name: proc_groupstatus
 ****************************************************************************/

static ssize_t proc_groupstatus(FAR struct proc_file_s *procfile,
                                FAR struct tcb_s *tcb, FAR char *buffer,
                                size_t buflen, off_t offset)
{
  FAR struct task_group_s *group = tcb->group;
  size_t remaining;
  size_t linesize;
  size_t copysize;
  size_t totalsize;
#ifdef HAVE_GROUP_MEMBERS
  int i;
#endif

  DEBUGASSERT(group != NULL);

  remaining = buflen;
  totalsize = 0;

  /* Show the group IDs */

#ifdef HAVE_GROUP_MEMBERS
  linesize   = snprintf(procfile->line, STATUS_LINELEN, "%-12s%d\n",
                        "Group ID:", group->tg_grpid);
  copysize   = procfs_memcpy(procfile->line, linesize, buffer,
                             remaining, &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }

  linesize   = snprintf(procfile->line, STATUS_LINELEN, "%-12s%d\n",
                        "Parent ID:", group->tg_pgrpid);
  copysize   = procfs_memcpy(procfile->line, linesize, buffer,
                             remaining, &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }
#endif

#if !defined(CONFIG_DISABLE_PTHREAD) && defined(CONFIG_SCHED_HAVE_PARENT)
  linesize   = snprintf(procfile->line, STATUS_LINELEN, "%-12s%d\n",
                        "Main task:", group->tg_task);
  copysize   = procfs_memcpy(procfile->line, linesize, buffer,
                             remaining, &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }
#endif

  linesize   = snprintf(procfile->line, STATUS_LINELEN, "%-12s0x%02x\n",
                        "Flags:", group->tg_flags);
  copysize   = procfs_memcpy(procfile->line, linesize, buffer,
                             remaining, &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }

  linesize   = snprintf(procfile->line, STATUS_LINELEN, "%-12s%d\n",
                        "Members:", group->tg_nmembers);
  copysize   = procfs_memcpy(procfile->line, linesize, buffer,
                            remaining, &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

#ifdef HAVE_GROUP_MEMBERS
  if (totalsize >= buflen)
    {
      return totalsize;
    }

  linesize   = snprintf(procfile->line, STATUS_LINELEN, "Member IDs:");
  copysize   = procfs_memcpy(procfile->line, linesize, buffer,
                             remaining, &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }

  for (i = 0; i < group->tg_nmembers; i++)
    {
      linesize   = snprintf(procfile->line, STATUS_LINELEN, " %d",
                            group->tg_members[i]);
      copysize   = procfs_memcpy(procfile->line, linesize, buffer,
                                 remaining, &offset);

      totalsize += copysize;
      buffer    += copysize;
      remaining -= copysize;

      if (totalsize >= buflen)
        {
          return totalsize;
        }
    }

  linesize   = snprintf(procfile->line, STATUS_LINELEN, "\n");
  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining,
                             &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;
#endif

  return totalsize;
}

/****************************************************************************
 * Name: proc_groupfd
 ****************************************************************************/

static ssize_t proc_groupfd(FAR struct proc_file_s *procfile,
                            FAR struct tcb_s *tcb, FAR char *buffer,
                            size_t buflen, off_t offset)
{
  FAR struct task_group_s *group = tcb->group;
  FAR struct file *file;
#ifdef CONFIG_NET
  FAR struct socket *socket;
#endif
  size_t remaining;
  size_t linesize;
  size_t copysize;
  size_t totalsize;
  int i;

  DEBUGASSERT(group != NULL);

  remaining = buflen;
  totalsize = 0;

  linesize   = snprintf(procfile->line, STATUS_LINELEN, "\n%-3s %-8s %s\n",
                        "FD", "POS", "OFLAGS");
  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining,
                             &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }

  /* Examine each open file descriptor */

  for (i = 0, file = group->tg_filelist.fl_files;
       i < CONFIG_NFILE_DESCRIPTORS;
       i++, file++)
    {
      /* Is there an inode associated with the file descriptor? */

      if (file->f_inode)
        {
          linesize   = snprintf(procfile->line, STATUS_LINELEN,
                                "%3d %8ld %04x\n", i, (long)file->f_pos,
                                file->f_oflags);
          copysize   = procfs_memcpy(procfile->line, linesize, buffer,
                                     remaining, &offset);

          totalsize += copysize;
          buffer    += copysize;
          remaining -= copysize;

          if (totalsize >= buflen)
            {
              return totalsize;
            }
        }
    }

#ifdef CONFIG_NET
  linesize   = snprintf(procfile->line, STATUS_LINELEN,
                        "\n%-3s %-2s %-3s %s\n",
                        "SD", "RF", "TYP", "FLAGS");
  copysize   = procfs_memcpy(procfile->line, linesize, buffer, remaining,
                             &offset);

  totalsize += copysize;
  buffer    += copysize;
  remaining -= copysize;

  if (totalsize >= buflen)
    {
      return totalsize;
    }

  /* Examine each open socket descriptor */

  for (i = 0, socket = group->tg_socketlist.sl_sockets;
       i < CONFIG_NSOCKET_DESCRIPTORS;
       i++, socket++)
    {
      /* Is there an connection associated with the socket descriptor? */

      if (socket->s_conn)
        {
          linesize   = snprintf(procfile->line, STATUS_LINELEN,
                                "%3d %2d %3d %02x",
                                i + CONFIG_NFILE_DESCRIPTORS,
                                socket->s_crefs, socket->s_type,
                                socket->s_flags);
          copysize   = procfs_memcpy(procfile->line, linesize, buffer,
                                     remaining, &offset);

          totalsize += copysize;
          buffer    += copysize;
          remaining -= copysize;

          if (totalsize >= buflen)
            {
              return totalsize;
            }
        }
    }
#endif

  return totalsize;
}

/****************************************************************************
 * Name: proc_groupenv_callback
 ****************************************************************************/

#if !defined(CONFIG_DISABLE_ENVIRON) && !defined(CONFIG_FS_PROCFS_EXCLUDE_ENVIRON)
static int proc_groupenv_callback(FAR void *arg, FAR const char *pair)
{
  FAR struct proc_envinfo_s *info = (FAR struct proc_envinfo_s *)arg;
  FAR const char *src;
  FAR const char *value;
  FAR char *dest;
  char name[16 + 1];
  size_t linesize;
  size_t copysize;
  int namelen;

  DEBUGASSERT(arg != NULL && pair != NULL);

  /* Parse the name from the name/value pair */

  value  = NULL;
  namelen = 0;

  for (src = pair, dest = name; *src != '=' && *src != '\0'; src++)
    {
      if (namelen < 16)
        {
          *dest++ = *src;
          namelen++;
        }
    }

  /* NUL terminate the name string */

  *dest = '\0';

  /* Skip over the '=' to get the value */

  if (*src == '=')
    {
      value = src + 1;
    }
  else
    {
      value = "";
    }

  /* Output the header */

  linesize        = snprintf(info->procfile->line, STATUS_LINELEN, "%s=%s\n",
                             name, value);
  copysize        = procfs_memcpy(info->procfile->line, linesize,
                                  info->buffer, info->remaining,
                                  &info->offset);

  info->totalsize += copysize;
  info->buffer    += copysize;
  info->remaining -= copysize;

  if (info->totalsize >= info->buflen)
    {
      return 1;
    }

  return 0;
}
#endif

/****************************************************************************
 * Name: proc_groupenv
 ****************************************************************************/

#if !defined(CONFIG_DISABLE_ENVIRON) && !defined(CONFIG_FS_PROCFS_EXCLUDE_ENVIRON)
static ssize_t proc_groupenv(FAR struct proc_file_s *procfile,
                 FAR struct tcb_s *tcb, FAR char *buffer, size_t buflen,
                 off_t offset)
{
  FAR struct task_group_s *group = tcb->group;
  struct proc_envinfo_s info;

  DEBUGASSERT(group != NULL);

  /* Initialize the info structure */

  info.procfile   = procfile;
  info.buffer     = buffer;
  info.offset     = offset;
  info.buflen     = buflen;
  info.remaining  = buflen;
  info.totalsize  = 0;

  /* Generate output for each environment variable */

  env_foreach(group, proc_groupenv_callback, &info);
  return info.totalsize;
}
#endif

/****************************************************************************
 * Name: proc_open
 ****************************************************************************/

static int proc_open(FAR struct file *filep, FAR const char *relpath,
                     int oflags, mode_t mode)
{
  FAR struct proc_file_s *procfile;
  FAR const struct proc_node_s *node;
  FAR struct tcb_s *tcb;
  FAR char *ptr;
  unsigned long tmp;
  pid_t pid;

  finfo("Open '%s'\n", relpath);

  /* PROCFS is read-only.  Any attempt to open with any kind of write
   * access is not permitted.
   *
   * REVISIT:  Write-able proc files could be quite useful.
   */

  if ((oflags & O_WRONLY) != 0 || (oflags & O_RDONLY) == 0)
    {
      ferr("ERROR: Only O_RDONLY supported\n");
      return -EACCES;
    }

  /* The first segment of the relative path should be a task/thread ID or
   * the string "self".
   */

  ptr = NULL;

  if (strncmp(relpath, "self", 4) == 0)
    {
      tmp = (unsigned long)getpid();    /* Get the PID of the calling task */
      ptr = (FAR char *)relpath + 4;    /* Discard const */
    }
  else
    {
      tmp = strtoul(relpath, &ptr, 10); /* Extract the PID from path */
    }

  if (ptr == NULL || *ptr != '/')
    {
      ferr("ERROR: Invalid path \"%s\"\n", relpath);
      return -ENOENT;
    }

  /* Skip over the slash */

  ptr++;

  /* A valid PID would be in the range of 0-32767 (0 is reserved for the
   * IDLE thread).
   */

  if (tmp >= 32768)
    {
      ferr("ERROR: Invalid PID %ld\n", tmp);
      return -ENOENT;
    }

  /* Now verify that a task with this task/thread ID exists */

  pid = (pid_t)tmp;

  tcb = nxsched_get_tcb(pid);
  if (tcb == NULL)
    {
      ferr("ERROR: PID %d is no longer valid\n", (int)pid);
      return -ENOENT;
    }

  /* The remaining segments of the relpath should be a well known node in
   * the task/thread tree.
   */

  node = proc_findnode(ptr);
  if (node == NULL)
    {
      ferr("ERROR: Invalid path \"%s\"\n", relpath);
      return -ENOENT;
    }

  /* The node must be a file, not a directory */

  if (!DIRENT_ISFILE(node->dtype))
    {
      ferr("ERROR: Path \"%s\" is not a regular file\n", relpath);
      return -EISDIR;
    }

  /* Allocate a container to hold the task and node selection */

  procfile = (FAR struct proc_file_s *)
    kmm_zalloc(sizeof(struct proc_file_s));
  if (procfile == NULL)
    {
      ferr("ERROR: Failed to allocate file container\n");
      return -ENOMEM;
    }

  /* Initialize the file container */

  procfile->pid  = pid;
  procfile->node = node;

  /* Save the index as the open-specific state in filep->f_priv */

  filep->f_priv = (FAR void *)procfile;
  return OK;
}

/****************************************************************************
 * Name: proc_close
 ****************************************************************************/

static int proc_close(FAR struct file *filep)
{
  FAR struct proc_file_s *procfile;

  /* Recover our private data from the struct file instance */

  procfile = (FAR struct proc_file_s *)filep->f_priv;
  DEBUGASSERT(procfile != NULL);

  /* Release the file container structure */

  kmm_free(procfile);
  filep->f_priv = NULL;
  return OK;
}

/****************************************************************************
 * Name: proc_read
 ****************************************************************************/

static ssize_t proc_read(FAR struct file *filep, FAR char *buffer,
                         size_t buflen)
{
  FAR struct proc_file_s *procfile;
  FAR struct tcb_s *tcb;
  ssize_t ret;

  finfo("buffer=%p buflen=%d\n", buffer, (int)buflen);

  /* Recover our private data from the struct file instance */

  procfile = (FAR struct proc_file_s *)filep->f_priv;
  DEBUGASSERT(procfile != NULL);

  /* Verify that the thread is still valid */

  tcb = nxsched_get_tcb(procfile->pid);
  if (tcb == NULL)
    {
      ferr("ERROR: PID %d is not valid\n", (int)procfile->pid);
      return -ENODEV;
    }

  /* Provide the requested data */

  switch (procfile->node->node)
    {
    case PROC_STATUS: /* Task/thread status */
      ret = proc_status(procfile, tcb, buffer, buflen, filep->f_pos);
      break;

    case PROC_CMDLINE: /* Task command line */
      ret = proc_cmdline(procfile, tcb, buffer, buflen, filep->f_pos);
      break;

#ifdef CONFIG_SCHED_CPULOAD
    case PROC_LOADAVG: /* Average CPU utilization */
      ret = proc_loadavg(procfile, tcb, buffer, buflen, filep->f_pos);
      break;
#endif
#ifdef CONFIG_SCHED_CRITMONITOR
    case PROC_CRITMON: /* Critical section monitor */
      ret = proc_critmon(procfile, tcb, buffer, buflen, filep->f_pos);
      break;
#endif
    case PROC_STACK: /* Task stack info */
      ret = proc_stack(procfile, tcb, buffer, buflen, filep->f_pos);
      break;

    case PROC_GROUP_STATUS: /* Task group status */
      ret = proc_groupstatus(procfile, tcb, buffer, buflen, filep->f_pos);
      break;

    case PROC_GROUP_FD: /* Group file descriptors */
      ret = proc_groupfd(procfile, tcb, buffer, buflen, filep->f_pos);
      break;

#if !defined(CONFIG_DISABLE_ENVIRON) && !defined(CONFIG_FS_PROCFS_EXCLUDE_ENVIRON)
    case PROC_GROUP_ENV: /* Group environment variables */
      ret = proc_groupenv(procfile, tcb, buffer, buflen, filep->f_pos);
      break;
#endif

     default:
      ret = -EINVAL;
      break;
    }

  /* Update the file offset */

  if (ret > 0)
    {
      filep->f_pos += ret;
    }

  return ret;
}

/****************************************************************************
 * Name: proc_dup
 *
 * Description:
 *   Duplicate open file data in the new file structure.
 *
 ****************************************************************************/

static int proc_dup(FAR const struct file *oldp, FAR struct file *newp)
{
  FAR struct proc_file_s *oldfile;
  FAR struct proc_file_s *newfile;

  finfo("Dup %p->%p\n", oldp, newp);

  /* Recover our private data from the old struct file instance */

  oldfile = (FAR struct proc_file_s *)oldp->f_priv;
  DEBUGASSERT(oldfile != NULL);

  /* Allocate a new container to hold the task and node selection */

  newfile = (FAR struct proc_file_s *)kmm_malloc(sizeof(struct proc_file_s));
  if (newfile == NULL)
    {
      ferr("ERROR: Failed to allocate file container\n");
      return -ENOMEM;
    }

  /* The copy the file information from the old container to the new */

  memcpy(newfile, oldfile, sizeof(struct proc_file_s));

  /* Save the new container in the new file structure */

  newp->f_priv = (FAR void *)newfile;
  return OK;
}

/****************************************************************************
 * Name: proc_opendir
 *
 * Description:
 *   Open a directory for read access
 *
 ****************************************************************************/

static int proc_opendir(FAR const char *relpath, FAR struct fs_dirent_s *dir)
{
  FAR struct proc_dir_s *procdir;
  FAR const struct proc_node_s *node;
  FAR struct tcb_s *tcb;
  unsigned long tmp;
  FAR char *ptr;
  pid_t pid;

  finfo("relpath: \"%s\"\n", relpath ? relpath : "NULL");
  DEBUGASSERT(relpath != NULL && dir != NULL && dir->u.procfs == NULL);

  /* The relative must be either:
   *
   *  (1) "<pid>" - The sub-directory of task/thread attributes,
   *  (2) "self"  - Which refers to the PID of the calling task, or
   *  (3) The name of a directory node under either of those
   */

  /* Otherwise, the relative path should be a valid task/thread ID */

  ptr = NULL;

  if (strncmp(relpath, "self", 4) == 0)
    {
      tmp = (unsigned long)getpid();    /* Get the PID of the calling task */
      ptr = (FAR char *)relpath + 4;    /* Discard const */
    }
  else
    {
      tmp = strtoul(relpath, &ptr, 10); /* Extract the PID from path */
    }

  if (ptr == NULL || (*ptr != '\0' && *ptr != '/'))
    {
      /* strtoul failed or there is something in the path after the pid */

      ferr("ERROR: Invalid path \"%s\"\n", relpath);
      return -ENOENT;
    }

  /* A valid PID would be in the range of 0-32767 (0 is reserved for the
   * IDLE thread).
   */

  if (tmp >= 32768)
    {
      ferr("ERROR: Invalid PID %ld\n", tmp);
      return -ENOENT;
    }

  /* Now verify that a task with this task/thread ID exists */

  pid = (pid_t)tmp;

  tcb = nxsched_get_tcb(pid);
  if (tcb == NULL)
    {
      ferr("ERROR: PID %d is not valid\n", (int)pid);
      return -ENOENT;
    }

  /* Allocate the directory structure.  Note that the index and procentry
   * pointer are implicitly nullified by kmm_zalloc().  Only the remaining,
   * non-zero entries will need be initialized.
   */

  procdir = (FAR struct proc_dir_s *)kmm_zalloc(sizeof(struct proc_dir_s));
  if (procdir == NULL)
    {
      ferr("ERROR: Failed to allocate the directory structure\n");
      return -ENOMEM;
    }

  /* Was the <pid> the final element of the path? */

  if (*ptr != '\0' && strcmp(ptr, "/") != 0)
    {
      /* There is something in the path after the pid.  Skip over the path
       * segment delimiter and see if we can identify the node of interest.
       */

      ptr++;
      node = proc_findnode(ptr);
      if (node == NULL)
        {
          ferr("ERROR: Invalid path \"%s\"\n", relpath);
          kmm_free(procdir);
          return -ENOENT;
        }

      /* The node must be a directory, not a file */

      if (!DIRENT_ISDIRECTORY(node->dtype))
        {
          ferr("ERROR: Path \"%s\" is not a directory\n", relpath);
          kmm_free(procdir);
          return -ENOTDIR;
        }

      /* This is a second level directory */

      procdir->base.level    = 2;
      procdir->base.nentries = PROC_NGROUPNODES;
      procdir->node          = node;
    }
  else
    {
      /* Use the special level0 node */

      procdir->base.level    = 1;
      procdir->base.nentries = PROC_NLEVEL0NODES;
      procdir->node          = &g_level0node;
    }

  procdir->pid  = pid;
  dir->u.procfs = (FAR void *)procdir;
  return OK;
}

/****************************************************************************
 * Name: proc_closedir
 *
 * Description: Close the directory listing
 *
 ****************************************************************************/

static int proc_closedir(FAR struct fs_dirent_s *dir)
{
  FAR struct proc_dir_s *priv;

  DEBUGASSERT(dir != NULL && dir->u.procfs != NULL);
  priv = dir->u.procfs;

  if (priv)
    {
      kmm_free(priv);
    }

  dir->u.procfs = NULL;
  return OK;
}

/****************************************************************************
 * Name: proc_readdir
 *
 * Description: Read the next directory entry
 *
 ****************************************************************************/

static int proc_readdir(struct fs_dirent_s *dir)
{
  FAR struct proc_dir_s *procdir;
  FAR const struct proc_node_s *node = NULL;
  FAR struct tcb_s *tcb;
  unsigned int index;
  pid_t pid;
  int ret;

  DEBUGASSERT(dir != NULL && dir->u.procfs != NULL);
  procdir = dir->u.procfs;

  /* Have we reached the end of the directory */

  index = procdir->base.index;
  if (index >= procdir->base.nentries)
    {
      /* We signal the end of the directory by returning the special
       * error -ENOENT
       */

      finfo("Entry %d: End of directory\n", index);
      ret = -ENOENT;
    }

  /* No, we are not at the end of the directory */

  else
    {
      /* Verify that the pid still refers to an active task/thread */

      pid = procdir->pid;

      tcb = nxsched_get_tcb(pid);
      if (tcb == NULL)
        {
          ferr("ERROR: PID %d is no longer valid\n", (int)pid);
          return -ENOENT;
        }

      /* The TCB is still valid (or at least was when we entered this
       * function)
       *
       * Handle the directory listing by the node type.
       */

      switch (procdir->node->node)
        {
         case PROC_LEVEL0: /* Top level directory */
           DEBUGASSERT(procdir->base.level == 1);
           node = g_level0info[index];
           break;

         case PROC_GROUP:  /* Group sub-directory */
           DEBUGASSERT(procdir->base.level == 2);
           node = g_groupinfo[index];
           break;

         default:
           return -ENOENT;
        }

      /* Save the filename and file type */

      dir->fd_dir.d_type = node->dtype;
      strncpy(dir->fd_dir.d_name, node->name, NAME_MAX + 1);

      /* Set up the next directory entry offset.  NOTE that we could use the
       * standard f_pos instead of our own private index.
       */

      procdir->base.index = index + 1;
      ret = OK;
    }

  return ret;
}

/****************************************************************************
 * Name: proc_rewindir
 *
 * Description: Reset directory read to the first entry
 *
 ****************************************************************************/

static int proc_rewinddir(struct fs_dirent_s *dir)
{
  FAR struct proc_dir_s *priv;

  DEBUGASSERT(dir != NULL && dir->u.procfs != NULL);
  priv = dir->u.procfs;

  priv->base.index = 0;
  return OK;
}

/****************************************************************************
 * Name: proc_stat
 *
 * Description: Return information about a file or directory
 *
 ****************************************************************************/

static int proc_stat(const char *relpath, struct stat *buf)
{
  FAR const struct proc_node_s *node;
  FAR struct tcb_s *tcb;
  unsigned long tmp;
  FAR char *ptr;
  pid_t pid;

  /* Two path forms are accepted:
   *
   * "<pid>" - If <pid> refers to a currently active task/thread, then it
   *   is a directory
   * "<pid>/<node>" - If <node> is a recognized node then, then it
   *   is a file or directory.
   */

  ptr = NULL;

  if (strncmp(relpath, "self", 4) == 0)
    {
      tmp = (unsigned long)getpid();    /* Get the PID of the calling task */
      ptr = (FAR char *)relpath + 4;    /* Discard const */
    }
  else
    {
      tmp = strtoul(relpath, &ptr, 10); /* Extract the PID from path */
    }

  if (ptr == NULL)
    {
      ferr("ERROR: Invalid path \"%s\"\n", relpath);
      return -ENOENT;
    }

  /* A valid PID would be in the range of 0-32767 (0 is reserved for the
   * IDLE thread).
   */

  if (tmp >= 32768)
    {
      ferr("ERROR: Invalid PID %ld\n", tmp);
      return -ENOENT;
    }

  /* Now verify that a task with this task/thread ID exists */

  pid = (pid_t)tmp;

  tcb = nxsched_get_tcb(pid);
  if (tcb == NULL)
    {
      ferr("ERROR: PID %d is no longer valid\n", (int)pid);
      return -ENOENT;
    }

  /* Was the <pid> the final element of the path? */

  memset(buf, 0, sizeof(struct stat));
  if (*ptr == '\0' || strcmp(ptr, "/") == 0)
    {
      /* Yes ... It's a read-only directory */

      buf->st_mode = S_IFDIR | S_IROTH | S_IRGRP | S_IRUSR;
    }

  /* Verify that the process ID is followed by valid path segment delimiter */

  else if (*ptr != '/')
    {
      /* We are required to return -ENOENT all all invalid paths */

      ferr("ERROR: Bad delimiter '%c' in relpath '%s'\n", *ptr, relpath);
      return -ENOENT;
    }
  else
    {
      /* Otherwise, the second segment of the relpath should be a well
       * known node of the task/thread directory structure.
       */

      /* Skip over the path segment delimiter */

      ptr++;

      /* Lookup the well-known node associated with the relative path. */

      node = proc_findnode(ptr);
      if (node == NULL)
        {
          ferr("ERROR: Invalid path \"%s\"\n", relpath);
          return -ENOENT;
        }

      /* If the node exists, it is the name for a read-only file or
       * directory.
       */

      if (node->dtype == DTYPE_FILE)
        {
          buf->st_mode = S_IFREG | S_IROTH | S_IRGRP | S_IRUSR;
        }
      else
        {
          buf->st_mode = S_IFDIR | S_IROTH | S_IRGRP | S_IRUSR;
        }
    }

  return OK;
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

#endif /* CONFIG_FS_PROCFS_EXCLUDE_PROCESS */
#endif /* !CONFIG_DISABLE_MOUNTPOINT && CONFIG_FS_PROCFS */
